package stalker.user;

import message.Message;
import message.controller.MessageController;
import message.visitor.UserMessageVisitor;
import network.e2e.FrameReader;
import network.e2e.FrameWriter;
import network.handler.StalkerNetListener;
import stalker.Stalker;
import catalog.UniqueNamedStalkerCatalog;

/**
 * Immutable class that identifies a user
 * <p>
 * Factory method
 */

public class User extends Stalker {
  
  private final static UniqueNamedStalkerCatalog catalog_ = new UniqueNamedStalkerCatalog();
  private MessageController<Void> user_controller_; //may be null before activated
  private Thread listener_, queue_manager_;
  private FrameReader reader_;
  private FrameWriter writer_;
  private boolean is_active_;
  
  private void checkRep() {
    assert catalog_ != null:
      "User: fields must not be null";
    assert catalog_.getIDFromName(this.getName()) == this.getID() :
      "User: Inconsistent data";
    assert Stalker.getStalker(this.getID()) == this :
      "User: Inconsistent data";
  }
  
  /**
   * Constructs a new user
   * @requires name not null and unique
   * @param name username of the new user
   * @complexity A(1)
   */
  private User(String name) {
    super(name);
    assert name != null;
    assert (!catalog_.contains(name));
    catalog_.add(this);
    reader_ = null;
    writer_ = null;
    queue_manager_ = null;
    listener_ = null;
    is_active_ = false;
    checkRep();
  }
  
  /**
   * Logs this stalker in the system, making it active
   * @requires both params not null
   * @param reader FrameReader for interaction
   * @param writer FrameWriter for interaction
   */
  public synchronized void activate(FrameReader reader, FrameWriter writer) {
    System.out.println("Activating...");
    assert reader != null && writer != null;
    deactivate();
    listener_ = new Thread(new StalkerNetListener(this, reader));
    user_controller_ = new MessageController<Void>(new UserMessageVisitor(this, writer));
    queue_manager_ = new Thread(user_controller_);
    queue_manager_.start();
    listener_.start();
    reader_ = reader;
    writer_ = writer;
    checkRep();
    System.out.println("Activated!");
    is_active_ = true;
  }
  
  /**
   * Logs this stalker off the system, making in inactive
   */
  public synchronized void deactivate() {
    System.out.println("Deactivating...");
    is_active_ = false;
    if (reader_ != null) {
      reader_ = null;
    }
    if (writer_ != null) {
      writer_ = null;
    }
    System.out.println("Closing queue manager...");
    if (queue_manager_ != null) {
      queue_manager_.interrupt();
      try {
        queue_manager_.join();
      } catch (InterruptedException e) {
        System.out.println("Interrupted again");
        e.printStackTrace();
      }
      queue_manager_ = null;
    }
    System.out.println("Closing listener...");
    if (listener_ != null) {
      listener_.interrupt();
      try {
        listener_.join();
      } catch (InterruptedException e) {
        System.out.println("Interrupted again");
        e.printStackTrace();
      }
      listener_ = null;
    }
    System.out.println("Deactivated!");
  }
  
  /**
   * Checks if user is logged in
   */
  public synchronized boolean isLoggedIn() {
    System.out.println("Checking if logged");
    if (!is_active_) {
      System.out.println("Checking done!");
      return false;
    }
    if (queue_manager_ == null || listener_ == null) {
      System.out.println("Checking done!");
      return false;
    }
    System.out.println("Checking done!");
    return queue_manager_.isAlive() && listener_.isAlive();
  }
  
  
  /**
   * Factory method for creating a new user
   * @requires name not null and unique
   * @param name username of the new user
   * @return a new user with the given username
   */
  public static User createUser(String name) {
    assert name != null;
    assert !catalog_.contains(name);
    return new User(name);
  }
  
  /**
   * Checks if a username is taken
   * @requires name not null
   * @param name username that's going to be checked
   * @return true iff username not taken
   */
  public static boolean isTaken(String name) {
    assert name != null;
    return catalog_.contains(name);
  }
  
  /**
   * Accepts a request
   * @requires request not null
   * @param request A request made to the room
   */

  @Override
  public void acceptMessage(Message message) {
    assert message != null;
    assert user_controller_ != null;
    if (!isLoggedIn()) {
      // TODO
    } else {
      user_controller_.enqueueMessage(message);
    }
  }
  
  /**
   * Retrieves a user given its user id
   * @param id user's id, must be present
   * @return User whose id is given
   */
  public static User getUserFromID(long id) {
    assert (Stalker.contains(id));
    assert (Stalker.getStalker(id) instanceof User);
    return (User)Stalker.getStalker(id);
  }
  
  /**
   * Retrieves a user given its name
   * @param name user's name, must be present
   * @return User whose name is given
   */
  public static User getUserFromName(String name) {
    assert (name != null);
    assert (catalog_.contains(name));
    return getUserFromID(catalog_.getIDFromName(name));
  }
  
}
